package mediafile

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"time"

	"mall/common/jwt"
	"mall/common/resultx"
	"mall/common/utils/pointy"
	"mall/common/utils/uuidx"
	"mall/service/attachment/rpc/attachmentclient"
	"mall/service/forum/api/internal/svc"
	"mall/service/forum/api/internal/types"

	"github.com/gabriel-vasile/mimetype"
	// "github.com/sirupsen/logrus"
	"github.com/zeromicro/go-zero/core/logx"
)

type UploadLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
	r      *http.Request
}

func NewUploadLogic(r *http.Request, svcCtx *svc.ServiceContext) *UploadLogic {
	ctx := r.Context()

	return &UploadLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
		r:      r,
	}
}

func (l *UploadLogic) Upload(headers *[]*types.Header) (resp *types.UploadResp, err error) {
	userId := jwt.GetUserId(l.ctx)
	if userId == 0 {
		return nil, resultx.NewErrCode(resultx.TOKEN_EXPIRE_ERROR)
	}

	uid := strconv.FormatInt(int64(userId), 10)

	// http.Request has a member MultipartForm, it's defined as:
	// MultipartForm *multipart.Form
	// type Form struct {
	//    Value map[string][]string
	//    File  map[string][]*multipart.FileHeader
	// }

	err = l.r.ParseMultipartForm(int64(l.svcCtx.Config.NfsConf.MaxVideoSize))
	if err != nil {
		logx.Errorw("Upload  fail to parse multipart form", logx.Field("detail", err.Error()))
		return nil, resultx.NewErrCode(resultx.UPLOAD_ERROR)
	}

	if l.r.MultipartForm.File == nil {
		logx.Error("Upload  file is empty")
		return nil, resultx.NewErrCode(resultx.UPLOAD_ERROR)
	}

	allowedFileExts := strings.Split(strings.ToLower(l.svcCtx.Config.NfsConf.AllowedFileExts), ",")
	var list []*types.UploadInfo
	for key, _ := range l.r.MultipartForm.File {
		// func (r *http.Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error)
		// FormFile returns the first file for the provided form key
		file, handler, err := l.r.FormFile(key)
		if err != nil {
			logx.Errorw(fmt.Sprintf("Upload  value of %s cannot be found", key),
				logx.Field("detail", err.Error()))
			return nil, resultx.NewErrCode(resultx.UPLOAD_ERROR)
		}

		defer file.Close()
		mime, err := mimetype.DetectReader(file)
		if err != nil {
			logx.Errorw(fmt.Sprintf("Upload  fail to parse the mimetype of %s", key),
				logx.Field("detail", err.Error()))
			return nil, resultx.NewErrCode(resultx.UPLOAD_ERROR)
		}

		//! Important: we *MUST* reset the reader back to 0, since `minetype.DetectReader` reads the beginning of the
		//! file in order to detect it's MIME type. Continuing to use the reader without resetting it results in a
		//! corrupted file unable to be processed or opened otherwise.
		if _, err = file.Seek(0, io.SeekStart); err != nil {
			logx.Errorw(fmt.Sprintf("Upload  fail to parse mimetype of %s", key),
				logx.Field("detail", err.Error()))
			return nil, resultx.NewErrCode(resultx.UPLOAD_ERROR)
		}

		fileExt := strings.TrimLeft(mime.Extension(), ".")
		mimeType := mime.String()
		// logrus.Info(fmt.Sprintf("Upload  key: %s, fileExt: %s, mimeType: %s", key, fileExt, mimeType))
		// Upload key: file, fileExt: jpg, mimeType: image/jpeg

		// 另一种方式
		// filetype := l.r.FormValue("filetype")
		// if len(filetype) > 1 {
		// 	fileExt = strings.TrimLeft(filetype, ".")
		// }

		//check if file type is allowed
		var allowed bool
		for _, ext := range allowedFileExts {
			// if mime.Is(ext) {
			if ext == "*" || ext == fileExt {
				allowed = true
				break
			}
		}
		if !allowed {
			return nil, resultx.NewErrCode(resultx.UPLOAD_WRONG_TYPE_ERROR)
		}

		// fileType := strings.Split(handler.Header.Get("Content-Type"), "/")[0]
		// logrus.Info(fmt.Sprintf("Upload fileType: %s", fileType))
		// Upload fileType: image

		//judge if the file size is over max size
		//判断文件大小是否超过设定值
		// fileType := strings.Split(handler.Header.Get("Content-Type"), "/")[0]
		fileType := strings.Split(mimeType, "/")[0]
		if fileType != "image" && fileType != "video" && fileType != "audio" {
			fileType = "other"
		}
		fileSize := uint64(handler.Size)
		fileName := handler.Filename
		if l.checkOverSize(fileType, fileSize) {
			logx.Errorw(
				"Upload  file is over size",
				logx.Field("type", fileType),
				logx.Field("size", fileSize),
				logx.Field("userId", uid),
				logx.Field("fileName", fileName),
			)
			return nil, resultx.NewErrCode(resultx.UPLOAD_OVER_SIZE_ERROR)
		}

		//judge if the suffix is legal
		//校验后缀是否合法
		// dotIndex := strings.LastIndex(handler.Filename, ".")
		// //if there is no suffix, reject it
		// //拒绝无后缀文件
		// if dotIndex == -1 {
		// 	logx.Errorw("Upload  reject file which does not have suffix")
		// 	return nil, resultx.NewErrCode(resultx.UPLOAD_WRONG_TYPE_ERROR)

		// fileName := handler.Filename[:dotIndex]
		// fileSuffix := handler.Filename[dotIndex+1:]

		fileUUID := uuidx.NewUUID()
		storeFileName := fileUUID.String() + "." + fileExt

		timeString := time.Now().Format("200601")

		//generate path
		//生成路径

		//judge if the file directory exists, if not create it. Both private and public need
		//to be created in order to move files when status changed
		//判断文件夹是否已创建, 同时创建好私人和公开文件夹防止文件状态改变时无法移动

		publicStoreDir := path.Join(l.svcCtx.Config.NfsConf.PublicStorePath, fileType, timeString)
		_, err = os.Stat(publicStoreDir)
		if err != nil {
			if os.IsNotExist(err) {
				if err = os.MkdirAll(publicStoreDir, 0775); err != nil {
					logx.Errorw("Upload  failed to create directory for storing public files", logx.Field("path", publicStoreDir))
					return nil, resultx.NewErrCode(resultx.UPLOAD_ERROR)
				}
			} else {
				logx.Errorw("Upload  failed to parse directory for storing public files", logx.Field("path", publicStoreDir))
				return nil, resultx.NewErrCode(resultx.UPLOAD_ERROR)
			}
		}

		//default is public
		//默认是公开的
		filePath := path.Join(publicStoreDir, storeFileName)
		targetFile, err := os.Create(filePath)
		if err != nil {
			logx.Errorw("Upload  fail to create file", logx.Field("path", filePath))
			return nil, resultx.NewErrCode(resultx.UPLOAD_ERROR)
		}

		defer targetFile.Close()
		_, err = io.Copy(targetFile, file)
		if err != nil {
			logx.Errorw("Upload  fail to create file", logx.Field("path", filePath))
			return nil, resultx.NewErrCode(resultx.UPLOAD_ERROR)
		}

		// targetFile.Chmod(0664)

		var relativeStorePath string = strings.TrimPrefix(l.svcCtx.Config.NfsConf.PublicStorePath, l.svcCtx.Config.NfsConf.PathPrefix)

		//store in database
		//提交数据库
		relativePath := fmt.Sprintf("%s/%s/%s/%s", relativeStorePath, fileType, timeString, storeFileName)

		var md5 string
		if len(l.r.MultipartForm.Value["md5"]) > 0 {
			md5 = l.r.MultipartForm.Value["md5"][0]
		}

		res, err := l.svcCtx.AttachmentRpc.BackendCreateFile(l.ctx, &attachmentclient.FileInfo{
			Filename: pointy.GetPointer(fileName),
			Path:     pointy.GetPointer(relativePath),
			MimeType: pointy.GetPointer(mimeType),
			Size:     pointy.GetPointer(fileSize),
			Md5:      pointy.GetPointer(md5),
			UserId:   pointy.GetPointer(uid),
		})
		if err != nil {
			return nil, err
		}

		list = append(list, &types.UploadInfo{
			Id:   pointy.GetPointer(res.Id),
			Name: pointy.GetPointer(fileName),
			Url:  pointy.GetPointer(l.svcCtx.Config.NfsConf.ProxyUrl + relativePath),
		})
	}

	if len(list) > 1 {
		return &types.UploadResp{
			Status: "done",
			List:   list,
		}, nil
	}

	// 兼容前端
	var itemId, itemName, itemUrl *string
	if len(list) == 1 {
		itemId = list[0].Id
		itemName = list[0].Name
		itemUrl = list[0].Url
	}

	return &types.UploadResp{
		Status: "done",
		Id:     itemId,
		Name:   itemName,
		Url:    itemUrl,
	}, nil
}

func (l *UploadLogic) checkOverSize(fileType string, size uint64) bool {
	switch fileType {
	case "image":
		if size > l.svcCtx.Config.NfsConf.MaxImageSize {
			return true
		}
	case "video":
		if size > l.svcCtx.Config.NfsConf.MaxVideoSize {
			return true
		}
	case "audio":
		if size > l.svcCtx.Config.NfsConf.MaxAudioSize {
			return true
		}
	case "other":
		if size > l.svcCtx.Config.NfsConf.MaxOtherSize {
			return true
		}
	}

	return false
}
